package com.shizuku.yaml.serializer;

import com.shizuku.yaml.annotaiton.YamlField;
import com.shizuku.yaml.exception.SerializeException;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

public class JavaBeanSerializer implements ObjectSerializer {
    public static final JavaBeanSerializer instance = new JavaBeanSerializer();

    @Override
    public void serialize(YamlSerializer serializer, Object object, int indentLevel) throws SerializeException {
        Class<?> clazz = object.getClass();
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            String key = field.getName();
            String name = key;
            YamlField a = field.getDeclaredAnnotation(YamlField.class);
            if (a != null) {
                if (!a.serialize()) {
                    continue;
                }
                name = getName(key, a);
            }

            if (indentLevel > 0) {
                serializer.builder.append("\n");
            }
            serializer.builder.append(" ".repeat(indentLevel * serializer.feature.getIntendSpace()));
            serializer.builder.append(name);
            serializer.builder.append(":");
            Object value;
            int mod = field.getModifiers();
            try {
                if (Modifier.isPublic(mod)) {
                    value = field.get(object);
                } else {
                    String getterMethodName = "get" + (char) (key.charAt(0) - 32) + key.substring(1);
                    Method method = clazz.getDeclaredMethod(getterMethodName);
                    value = method.invoke(object);
                }
                serializer.map(value, indentLevel + 1);
                if (indentLevel == 0) serializer.builder.append("\n");
            } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
                throw new SerializeException(clazz.getName() + "." + key + ": filed or getter not exist or can not access.");
            }
        }
    }

    private String getName(String key, YamlField yf) {
        if (yf.name().isEmpty()) {
            if (yf.format() == YamlField.Format.KEBAB) {
                return toKebab(key);
            } else if (yf.format() == YamlField.Format.PASCAL) {
                return toPascal(key);
            } else {
                return key;
            }
        } else {
            return yf.name();
        }
    }

    private String toKebab(String p) {
        StringBuilder r = new StringBuilder();
        for (int i = 0; i < p.length(); ++i) {
            char c = p.charAt(i);
            if (c >= 'A' && c <= 'Z') {
                r.append('_');
                r.append((char) (c + 32));
            } else {
                r.append(c);
            }
        }
        return r.toString();
    }

    private String toPascal(String k) {
        StringBuilder r = new StringBuilder();
        for (int i = 0; i < k.length(); ++i) {
            char c = k.charAt(i);
            if (c == '_') {
                i++;
                char x = k.charAt(i);
                r.append((char) (x - 32));
            } else {
                r.append(c);
            }
        }
        return r.toString();
    }
}
